﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WASapTKB.Domain
{
    public class Individual
    {
        // Mã số.
        public int Id = -1;
        // Số lỗi.
        public int NumError = 0;
        // Thời khóa biểu.
        public List<int> Schedule = new List<int>();
        // Danh sách tiết phạm lỗi.
        public Dictionary<int, int> PeriodErrors = new Dictionary<int, int>();

        private InputData inputData = InputData.Instance();

        public Individual()
        {
        }
        public Individual(List<int> pLstEvent)
        {
            int i = 0;
            foreach (var item in pLstEvent)
            {
                Schedule.Add(item); // Thêm sự kiện vào thời khóa biểu.
                PeriodErrors.Add(i++, 0);// Gán lỗi cho tiết thứ i.
            }
            while (Schedule.Count < inputData.NumWeekPeriodTotal)
            {
                Schedule.Add(-1); // Thêm sự kiện vào thời khóa biểu.
                PeriodErrors.Add(i++, -1);// Gán lỗi cho tiết thứ i.
            }
        }// end Schedule

        public Individual(List<int> pLstEvent, int pPeriod)
        {
            List<int> lstIndexEvent = new List<int>();// Danh sách chỉ số sự kiện đã thêm vào thời khóa biểu.
            int iEventValue, iUnitError;
            // Tạo bản sao của tập sự kiện.
            List<int> lstEvent = new List<int>();
            for (int i = 0; i < pLstEvent.Count; i++)
            {
                lstEvent.Add(pLstEvent[i]);
            }
            // Lặp qua tất cả các tiết trong thời khóa biểu.
            for (int i = 0; i < inputData.NumWeekPeriodTotal; i++)
            {
                iEventValue =
                iUnitError = -1;// Nếu tiết rỗng thì không xét lỗi.
                if (lstEvent.Count > 0)
                {
                    int iIndex = (new Random()).Next(0, lstEvent.Count - 1); // Lấy ngẫu nhiên 1 sự kiện.
                    iEventValue = lstEvent[iIndex];
                    iUnitError = 0;// Khởi tạo mọi tiết đều chưa phạm lỗi.
                    lstEvent.RemoveAt(iIndex);// Nếu đã lấy hết số tiết thì loại bỏ sự kiện.
                }// end if
                Schedule.Add(iEventValue); // Thêm sự kiện vào thời khóa biểu.
                PeriodErrors.Add(i, iUnitError);// Gán lỗi cho tiết thứ i.
            }// end for
        }// end Schedule

        #region Các ràng buộc của bài toán.
        // Ràng buộc C1
        public int ConstraintC1()
        {
            return 0;
        }
        // Ràng buộc C2: Giảng viên không thể dạy ở nhiều phòng cùng thời điểm.
        public int ConstraintC2()
        {
            int result = 0;
            int iUnit = inputData.NumWeekPeriod;
            int iEventValue, iIndexTeacher;
            int iEventValue1, iIndexTeacher1;
            // Lặp qua từng tiết theo phòng.
            for (int i = 0; i < iUnit; i++)
            {
                List<int> lstTeacher = new List<int>();
                List<int> PeriodError = new List<int>();
                // Lặp qua các phòng.
                for (int j = 0; j < inputData.NumRoom - 1; j++)
                {
                    iEventValue = j * iUnit + i;// Lấy tiết của phòng j.
                    if (Schedule[iEventValue] == -1) continue;
                    iEventValue1 = (j + 1) * iUnit + i;// Lấy tiết của phòng j + 1.
                    // Lấy index của giảng viên ứng với 2 sự kiện đang xét.
                    iIndexTeacher = inputData.indexTeacher(Schedule[iEventValue]);
                    iIndexTeacher1 = inputData.indexTeacher(Schedule[iEventValue1]);
                    // Nếu 2 giảng viên giống nhau thì 2 tiết đó có lỗi.
                    if (iIndexTeacher == iIndexTeacher1)
                    {
                        if (!PeriodError.Contains(iEventValue))
                            PeriodError.Add(iEventValue);
                        if (!PeriodError.Contains(iEventValue1))
                            PeriodError.Add(iEventValue1);
                    }// end if
                }// end for j
                // Đánh dấu các tiết lỗi.
                foreach (var item in PeriodError)
                {
                    result++;
                    PeriodErrors[item] += 1;
                }
            }// end for i
            return result;
        }
        // Ràng buộc C3: Sức chứa của phòng phải đủ chỗ ngồi cho lớp
        public int ConstraintC3()
        {
            int result = 0;
            int iUnit = inputData.NumWeekPeriodTotal;// Tổng số tiết trong thời khóa biểu.
            for (int i = 0; i < iUnit; i++)
            {
                if (Schedule[i] == -1) continue;
                int iIndexClass = inputData.indexClass(Schedule[i]); // Lấy index của nhóm lớp ứng với sự kiện đang xét.
                int iIndexRoom = inputData.indexRoom(i); // Lấy index của phòng học từ chỉ số của sự kiện.
                if (inputData.LstUnitClass[iIndexClass] > inputData.LstUnitRoom[iIndexRoom])
                {
                    result++;
                    PeriodErrors[i] += 1;
                }
            }
            return result;
        }

        //// Kiểm tra số tiết lặp lại
        //bool CheckEventValue(List<int> pLstSchedule, int pEventValue, int pIndex, int pLength)
        //{
        //    for (int i = pIndex; i < pLength; i++)
        //    {
        //        if (pLstSchedule[i] != pEventValue)// Nếu có 1 giá trị khác với giá trị sự kiện truyền vào thì trả về false.
        //            return false;
        //    }
        //    return true;
        //}

        // Ràng buộc C4: Mỗi sự kiện phải lặp lại 2 hoặc 3 lần (ứng với 2 hoặc 3 tiết học) trong buổi học.
        public int ConstraintC4()
        {
            int result = 0;
            int iDurationCount = inputData.NumDay * inputData.NumSession;// Tổng số buổi trong tuần.
            int iUnit = 0;// Chỉ số tiết cho toàn thời khóa biểu.
            int k, iEventValue, iSubjectCount;// Chỉ số tiết, giá trị sự kiện, số tiết của môn tương ứng.
            // Lặp qua các phòng học.
            for (int i = 0; i < inputData.NumRoom; i++)
            {
                // Lặp qua các buổi học (10 buổi: 0 -> 9).
                for (int j = 0; j < iDurationCount; j++)
                {
                    // Lặp qua các tiết trong buổi.
                    for (k = 0; k < inputData.NumPeriod; k++)
                    {
                        iEventValue = Schedule[iUnit];
                        if (iEventValue != -1)// Nếu tiết khác rỗng thì mới xét.
                        {
                            // Lấy số tiết của môn học trong sự kiện.
                            iSubjectCount = inputData.LstSubjectUnit[inputData.indexSubject(iEventValue)];
                            // Nếu số tiết còn lại nhỏ hơn số tiết mà môn yêu cầu thì phạt.
                            if ((inputData.NumPeriod - k) < iSubjectCount)
                            {
                                result++;
                                PeriodErrors[iUnit] += 1;
                            }
                            else
                            {
                                List<int> lstUnitErrorTemp = new List<int>();// Danh tiết phạm lỗi tạm thời.
                                lstUnitErrorTemp.Add(iUnit);// Đầu tiên là tiết đang xét.
                                // Xét các tiết tiếp theo có cùng giá trị không.
                                for (int t = 1; t < iSubjectCount; t++)
                                {
                                    if (Schedule[iUnit + 1] != iEventValue)// Nếu không cùng giá trị.
                                    {
                                        break;
                                    }
                                    else
                                    {
                                        k++;
                                        iUnit++;
                                        lstUnitErrorTemp.Add(iUnit);
                                    }
                                }// endfor
                                // Nếu số tiết phạm lỗi tạm thời < số tiết của môn thì xem như các tiết đó đều phạm lỗi.
                                if (lstUnitErrorTemp.Count < iSubjectCount)
                                {
                                    foreach (var item in lstUnitErrorTemp)
                                    {
                                        result++;
                                        PeriodErrors[item] += 1;
                                    }
                                }
                            }// endelse
                        }// endif
                        iUnit++;// Xét tiết tiếp theo.
                    }// endfor tiết
                    if (k == inputData.NumPeriod)// Tiết cuối cùng trong buổi là rỗng.
                        continue;// Qua buổi tiếp theo.
                }
            }
            return result;
        }
        // Ràng buộc C5
        public int ConstraintC5()
        {
            return 0;
        }
        #endregion Các ràng buộc của bài toán.
    }// end Class
}// end Namespace
